﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace GeneticAlgorithm
{
    public class GeneticCycler
    {
        #region Properties

        public List<Configuration> Population { get; private set; }
        public List<SelectedConfiguration> EvaluatedPopulation { get; private set; }

        public int PopulationSize
        {
            get { return _populationSize; }
            set { _populationSize = value; }
        }

        public int LastGeneration
        {
            get { return _currentGeneration - 1; }
        }

        public int ToDoGeneration
        {
            get { return _currentGeneration + 1; }
        }

        public int CurrentGeneration
        {
            get { return _currentGeneration; }
        }

        public int CycleCount
        {
            get { return _cycleCount; }
            set { _cycleCount = value; }
        }

        #endregion

        #region Events

        public event OnCicleDoneHandler OnClicleDone;

        #endregion

        #region Delegates

        public delegate void OnCicleDoneHandler(GeneticCycler sender, GeneticCyclerArgs e);

        #endregion

        #region Members

        private ISelector _selector;
        private IGenericSimulator _simulator;
        readonly Random _random = new Random();

        private int _populationSize = 10;
        private int _currentGeneration = 0;
        private int _cycleCount = 1;
        private int _retryCount = 3;

        #endregion

        #region Constructors

        public GeneticCycler(IGenericSimulator simulator, ISelector selector)
        {
            Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
            _simulator = simulator;
            _selector = selector;
        }

        #endregion

        #region Public Methods

        public void Initialize()
        {         
            Population = _simulator.GetInitialConfigurations(PopulationSize);
        }

        public void Run()
        {
            var cycles = CycleCount;
            for (var i = 0; i < cycles; i++)
            {
                Step();
                CycleCount--;
            }
        }

        /// <summary>
        /// Makes one cycle in the genetic algorithm resulting in simulating current population, selecting parents,
        /// doing the cross over and finally the mutations
        /// Raises OnCicleDone
        /// </summary>
        public void Step()
        {
            var currentPopulation = new List<Configuration>(Population);


            try
            {
                var simulatedPopulation = DoSimulation(currentPopulation);

                var selectedPopulation = DoSelection(simulatedPopulation);

                var crossoveredPopulation = DoCrossover(new List<Configuration>(selectedPopulation));

                var mutatedPopulation = DoMutation(crossoveredPopulation);

                Population = mutatedPopulation;

                _currentGeneration++;

                var args = new GeneticCyclerArgs(currentPopulation, simulatedPopulation, selectedPopulation,
                    mutatedPopulation, _currentGeneration, CycleCount);
                if (OnClicleDone != null)
                    OnClicleDone(this, args);
            }
            catch (SimulationFailedException e)
            {
                _retryCount--;
                if (_retryCount != 0)
                    Step();
            }
            catch (Exception)
            {
                _retryCount = 3;
            }

            _retryCount = 3;
        }

        #endregion

        #region Private Methods

        private List<SimulatedConfiguration> DoSimulation(List<Configuration> population)
        {
            _simulator.Input = population;
            _simulator.Run();
            return _simulator.Output;
        }

        private List<Configuration> DoSelection(List<SimulatedConfiguration> simulatedPopulation)
        {
            _selector.Input = simulatedPopulation;
            _selector.PopulationSize = _populationSize;
            _selector.Run();

            return _selector.Output;
        }

        private List<Configuration> DoCrossover(List<Configuration> selectedPopulation)
        {
            var input = new List<Configuration>(selectedPopulation);
            input = new List<Configuration>(input.OrderBy((item) => _random.Next()));

            var output = new List<Configuration>();
            for (int i = 0; i < input.Count; i += 2)
            {
                var parentOne = input[i];
                var parentTwo = input[i + 1];

                var children = GetChildren(parentOne, parentTwo);

                output.AddRange(children);
            }

            return output;
        }

        private IEnumerable<Configuration> GetChildren(Configuration parentOne, Configuration parentTwo)
        {

            var parameters = _simulator.ChangebleParameters;
            var parentOneParametersToTakeCount = parameters.Count / 2;
            var parentTwoParametersToTakeCount = parameters.Count - parentOneParametersToTakeCount;

            var childOneConfig = _simulator.ConfigurationFactory();
            var childTwoConfig = _simulator.ConfigurationFactory();

            for (int i = 0; i < parentOneParametersToTakeCount; i++)
            {
                childOneConfig.Add(parameters[i], parentOne.GetString(parameters[i]));
                childTwoConfig.Add(parameters[i], parentTwo.GetString(parameters[i]));
            }

            for (int i = parentOneParametersToTakeCount; i < parentOneParametersToTakeCount + parentTwoParametersToTakeCount; i++)
            {
                childOneConfig.Add(parameters[i], parentTwo.GetString(parameters[i]));
                childTwoConfig.Add(parameters[i], parentOne.GetString(parameters[i]));
            }

            foreach (var unchangebleParameter in _simulator.UnchangebleParameters)
            {
                childOneConfig.Add(unchangebleParameter, parentOne.GetString(unchangebleParameter));
                childTwoConfig.Add(unchangebleParameter, parentTwo.GetString(unchangebleParameter));
            }

            return new List<Configuration>() { childOneConfig, childTwoConfig };
        }

        private List<Configuration> DoMutation(List<Configuration> crossoveredPopulation)
        {
            var inChildren = new List<Configuration>(crossoveredPopulation);
            foreach (var configuration in inChildren)
            {
                _simulator.RandomizeConfiguration(configuration, 1);
            }
            return inChildren;
        }

        #endregion
    }
}
